package com.mytijian.openapi.adapter.alihalt.service.impl;

import java.math.BigDecimal;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.mytijian.code.dto.QueryItemIdByCodeResult;
import com.mytijian.code.param.alihalt.QueryItemIdByCodeParam;
import com.mytijian.code.param.alihalt.ToTBAvailableParam;
import com.mytijian.code.param.alihalt.ToTBConsumeParam;
import com.mytijian.code.service.MerchantService;
import com.mytijian.openapi.adapter.alihalt.AlihaltMcodeHelper;
import com.mytijian.openapi.adapter.alihalt.consts.AlihaltConsts;
import com.mytijian.openapi.adapter.alihalt.dao.McodeOrderRelDAO;
import com.mytijian.openapi.adapter.alihalt.exception.AlihaltException;
import com.mytijian.openapi.adapter.alihalt.model.McodeInfo;
import com.mytijian.openapi.adapter.alihalt.param.AlihaltBookParam;
import com.mytijian.openapi.adapter.alihalt.result.AlihaltBookResult;
import com.mytijian.openapi.adapter.alihalt.service.AlihaltOrderBookService;
import com.mytijian.openapi.core.client.IOpenAPIClient;
import com.mytijian.openapi.core.client.param.BookParam;
import com.mytijian.openapi.core.client.param.RegisterParam;
import com.mytijian.openapi.core.client.result.BaseResult;
import com.mytijian.openapi.core.client.result.BookResult;
import com.mytijian.openapi.core.client.result.MealInfo;
import com.mytijian.openapi.core.client.result.MealListResult;
import com.mytijian.openapi.core.client.result.RegisterResult;
import com.mytijian.openapi.core.model.AppConfig;
import com.mytijian.openapi.core.service.OpenApiAppConfigService;
import com.mytijian.openapi.core.utils.MD5;
import com.mytijian.utils.Lists;

@Service
public class AlihaltOrderBookServiceImpl implements AlihaltOrderBookService {

	private Logger log = LoggerFactory.getLogger(getClass());

	@Autowired
	private OpenApiAppConfigService openApiService;

	@Autowired
	private McodeOrderRelDAO mcodeOrderRelDAO;

	@Autowired
	private MerchantService merchantService;
	
	@Autowired
	private IOpenAPIClient client;
	
	@Autowired
	private AlihaltMcodeHelper mcodeHelper;
	

	@Override
	public AlihaltBookResult book(AlihaltBookParam param) {

		// 下单链路较长
		// 使用 unifyKey 区分单个请求
		String unifyKey = UUID.randomUUID().toString();

		check(param, unifyKey);

		AppConfig config = openApiService.getConfig(AlihaltConsts.AppName);

		String openId = updateUserInfoAndGetOpenId(param, config, unifyKey);

		McodeInfo mcodeInfo = mcodeHelper.getMealId(param.getServiceNumber());
		// 获取套餐信息
		Integer mealId = mcodeInfo.getRefMealId(); //Integer.valueOf(param.getPackageCode());
		
		MealInfo meal = getMealInfo(unifyKey, config, mealId);

		// 使用开放平台接口下单
		BookResult bookResult = bookOrder(param, config, openId, meal, param.getServiceNumber());

		verifyBookResult(bookResult, unifyKey);

		String orderNum = bookResult.getOrderNum();

		AlihaltBookResult returnResult = buildResponse(meal, bookResult);

		buildMcodeOrderRel(config, param.getServiceNumber(), orderNum, unifyKey);
		
		handleMcodeConsume(param, config, orderNum, unifyKey);

		return returnResult;
	}

	private MealInfo getMealInfo(String unifyKey, AppConfig config, Integer mealId) {
		MealListResult mealResult = client.getMeals(config, mealId);

		if (mealResult==null || !StringUtils.isEmpty(mealResult.getErrCode()) || Lists.isEmpty(mealResult.getMeals())) {
			log.error("*** [阿里健康] [下单] unifykey:{} mealId:{} 未找到该套餐", unifyKey, mealId);
			throw new AlihaltException("500", "下单失败: 未查询到可用套餐" + mealId);
		}

		MealInfo meal = mealResult.getMeals().get(0);
		return meal;
	}

	private void handleMcodeConsume(AlihaltBookParam param, AppConfig config, String orderNum, String unifykey) {
		if (!mcodeConsume(param, unifykey)) {
			if (!revoke(config, orderNum)) {
				log.error("!!! [阿里健康] [下单] unifykey:{} 电子码[{}]下单后核销失败，后续撤单失败，需要人工介入处理。订单号:[{}]", unifykey, param.getServiceNumber(), orderNum);
				throw new AlihaltException("500", "电子码核销失败");
			} else {
				log.warn("** [阿里健康] [下单] unifykey:{} 电子码[{}]下单后核销失败, 订单已成功撤销。 订单号:[{}]，请知悉。", unifykey, param.getServiceNumber(), orderNum);
			}
		}
	}

	private boolean mcodeConsume(AlihaltBookParam param, String unifyKey) {
		try {
			ToTBConsumeParam ttp = new ToTBConsumeParam();
			ttp.setCode(param.getServiceNumber());
			merchantService.consume(ttp);
			return true;
		} catch (Exception e) {
			log.warn("*** [阿里健康] [下单] unifykey:{} 电子码核销失败 电子码:[{}]", unifyKey,  param.getServiceNumber());
			return false;
		}
	}

	/**
	 * <pre>
	 * 下单前校验 
	 * 	1. 验证 mcode 和 token是否匹配 
	 * 	2. 验证 mcode 是否可用
	 *  3. 验证必须参数
	 *  	1. hospitalId
	 * 
	 * @param param
	 */
	private void check(AlihaltBookParam param, String unifyKey) {
		
		if (StringUtils.isEmpty(param.getPeriodId()) || !StringUtils.isNumeric(param.getPeriodId())) {
			throw new AlihaltException("400", "时段ID[preiodId]必须是一个数字 传入["+param.getPeriodId()+"]");
		}
		
//		if (StringUtils.isEmpty(param.getPackageCode()) || !StringUtils.isNumeric(param.getPackageCode())) {
//			throw new AlihaltException("400", "套餐ID[packageCode]必须是一个数字 传入["+param.getPackageCode()+"]");
//		}
		
		// XXX 使用正则以外的办法判断金额或者
//		if (StringUtils.isEmpty(param.getPackageAmount()) || !param.getPackageAmount().matches("^(0|([^0]\\d*))(\\.\\d{1,2})?$")) {
//			throw new AlihaltException("400", "订单价格[packageAmount]必须是一个数字 传入["+param.getPackageAmount()+"]");
//		}
		
		String mcode = param.getServiceNumber();

		ToTBAvailableParam testCode = new ToTBAvailableParam();
		testCode.setCode(mcode);
		try {
			merchantService.available(testCode);
		} catch (Exception e) {
			log.warn("*** [阿里健康] [下单] unifykey:{} 电子码[{}]校验不通过。", unifyKey, mcode);
			throw new AlihaltException("400", "电子码无效");
		}
	}
	
	private void buildMcodeOrderRel(AppConfig config, String mcode, String orderNum, String unifyKey) {
		
		int insertCount = 0;

		try {
			insertCount = mcodeOrderRelDAO.insertMcodeOrderRel(mcode, orderNum);
		} catch (Exception e) {
			log.error("*** [阿里健康] [下单] 下单后绑定电子码关系失败 unfiykey:{} 电子码:[{}] 订单号:[{}] 异常信息: [{}]",unifyKey, mcode, orderNum, e);
		}

		if (insertCount == 0) {
			if (!revoke(config, orderNum)) {
				log.error("!!! [阿里健康] [下单] unifykey:{} 订单下单成功，绑定电子码失败。后续撤单失败，需人工处理。订单号:{}, 电子码:{}", unifyKey, orderNum, mcode);
			} else {
				log.error("*** [阿里健康] [下单] unifykey:{} 订单下单成功，绑定电子码失败。后续撤单成功。订单号:{}, 电子码:{}", unifyKey, orderNum, mcode);
			}
			throw new AlihaltException("500", "订单-电子码绑定关系失败");
		}
	}

	private void verifyBookResult(BookResult b, String unifyKey) {
		if (b==null) {
			log.error("[阿里健康] [下单] 下单失败 unifykey:{} 开放平台未返回数据", unifyKey);
			throw new AlihaltException("500", "调用开放平台失败");
		}
		if (StringUtils.isNotEmpty(b.getErrCode())) {
			log.error("[阿里健康] [下单] 下单失败 unifykey:{} openapi返回: {}", unifyKey, JSON.toJSONString(b));
			throw new AlihaltException("500", b.getErrMsg());
		}

		if (StringUtils.isEmpty(b.getOrderNum())) {
			log.error("[阿里健康] [下单] 下单失败 unifykey:{} openapi返回: {}", unifyKey, JSON.toJSONString(b));
			throw new AlihaltException("500", b.getErrMsg());
		}
	}

	/**
	 * 撤单
	 * 
	 * @param config
	 * @param orderNum
	 * @return
	 */
	private boolean revoke(AppConfig config, String orderNum) {
		BaseResult revokeResult = client.revoke(config, orderNum);
		return revokeResult != null && "T".equals(revokeResult.getSuccess());
	}

	private AlihaltBookResult buildResponse(MealInfo meal, BookResult b) {
		AlihaltBookResult r = new AlihaltBookResult();
		r.setResponseCode("200");
		r.setHospitalId(String.valueOf(meal.getHospitalId()));
		r.setHospitalName(meal.getHospitalName());
		r.setPackageName(meal.getName());
		r.setReserveAmount(String.valueOf(b.getOrderPrice()));
		r.setReserveDate(b.getExamDate());
		r.setReserveStatus(b.getOrderStatus());
		r.setReserveOrderNum(b.getOrderNum());
		return r;
	}

	private BookResult bookOrder(AlihaltBookParam param, AppConfig config, String openId, MealInfo meal, String mcode) {
		
		String tmallProductId = getTmallProductId(mcode);
		BookParam bookParam = new BookParam();
		bookParam.setExamAccountName(MD5.stringMD5(param.getName() + config.getAppSignKey()));
		bookParam.setIdCard(MD5.stringMD5(param.getIdCard() + config.getAppSignKey()));
		bookParam.setMobile(MD5.stringMD5(param.getMobilePhone() + config.getAppSignKey()));

		bookParam.setOrderPrice(meal.getPrice());
		
		bookParam.setExamTimeIntervalId(Integer.valueOf(param.getPeriodId()));

		bookParam.setMealId(meal.getMealId());
		bookParam.setOpenId(openId);

		bookParam.setExamDate(param.getReservationDate());

		// XXX 对方未给价格
		bookParam.setAgencySellingPrice(convertStringMoneyToInteger(param.getPackageAmount()));

		bookParam.setAgencyGoodsId(tmallProductId);
		
		BookResult b = client.book(config, bookParam);
		return b;
	}
	
	private Integer convertStringMoneyToInteger(String money) {
		if (money!=null && money.matches("^(0|([^0]\\d*))(\\.\\d{1,2})?$") ) {
			BigDecimal bd = new BigDecimal(money);
			BigDecimal result = bd.multiply(new BigDecimal("100"));
		return result.intValue();
		} else {
			return null;
		}
	}

	/**
	 * 获取openId
	 * 
	 * 1. 调用注册接口获取openID
	 * 
	 * @param param
	 * @param config
	 * @return
	 */
	private String updateUserInfoAndGetOpenId(AlihaltBookParam param, AppConfig config, String unifyKey) {
		RegisterParam userInfo = new RegisterParam();
		userInfo.setUsername(param.getName());
		userInfo.setIdcard(param.getIdCard());
		userInfo.setMobile(param.getMobilePhone());
		RegisterResult userResult = client.register(config, userInfo);
		
		if (userResult==null || StringUtils.isEmpty(userResult.getOpenId())) {
			log.warn("*** [阿里健康] [下单] unifykey:{} 电子码[{}] 注册/更新用户信息失败，开发平台返回: {}",unifyKey, param.getServiceNumber(), JSON.toJSON(userResult));
			throw new AlihaltException("500", "调用开放平台保存用户信息失败");
		}

		return userResult.getOpenId();
	}
	
	private String getTmallProductId(String mcode) {
		QueryItemIdByCodeParam qp = new QueryItemIdByCodeParam();
		qp.setCode(mcode);
		QueryItemIdByCodeResult tmprod = null;

		try {
			tmprod = merchantService.queryItemIdByCode(qp);
		} catch (RuntimeException e) {
			throw new AlihaltException("400", e.getMessage());
		}

		if (tmprod == null || StringUtils.isEmpty(tmprod.getItemId())) {
			throw new AlihaltException("400", "电子码无效");
		}

		String tmallProductId = tmprod.getItemId();
		return tmallProductId;
	}


}
